using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NodeCanvas.Variables;
using UnityEngine;

namespace NodeCanvas
{
	public abstract class Graph : MonoBehaviour, ITaskSystem
	{
		[SerializeField]
		private string _graphName = string.Empty;

		[SerializeField]
		private Node _primeNode;

		[SerializeField]
		private List<Node> _allNodes = new List<Node>();

		[SerializeField]
		private Component _agent;

		[SerializeField]
		private Blackboard _blackboard;

		[HideInInspector]
		public Transform _nodesRoot;

		private static List<Graph> runningGraphs = new List<Graph>();

		private float timeStarted;

		private Action FinishCallback;

		public new string name
		{
			get
			{
				return graphName;
			}
		}

		public float elapsedTime
		{
			get
			{
				return (!isRunning && !isPaused) ? 0f : (Time.time - timeStarted);
			}
		}

		public string graphName
		{
			get
			{
				if (string.IsNullOrEmpty(_graphName))
				{
					_graphName = base.gameObject.name;
				}
				return _graphName;
			}
			set
			{
				_graphName = value;
			}
		}

		public virtual Type baseNodeType
		{
			get
			{
				return typeof(Node);
			}
		}

		protected virtual bool allowNullAgent
		{
			get
			{
				return false;
			}
		}

		public Node primeNode
		{
			get
			{
				return _primeNode;
			}
			set
			{
				if ((bool)value && !value.allowAsPrime)
				{
					Debug.Log("Node '" + value.nodeName + "' can't be set as Start");
					return;
				}
				if (isRunning)
				{
					if (_primeNode != null)
					{
						_primeNode.ResetNode();
					}
					if (value != null)
					{
						value.ResetNode();
					}
				}
				_primeNode = value;
				UpdateNodeIDsInGraph();
				ReorderNodeList();
			}
		}

		public List<Node> allNodes
		{
			get
			{
				return _allNodes;
			}
			private set
			{
				_allNodes = value;
			}
		}

		public Component agent
		{
			get
			{
				return _agent;
			}
			set
			{
				if (_agent != value)
				{
					_agent = value;
					SendTaskOwnerDefaults();
				}
			}
		}

		public Blackboard blackboard
		{
			get
			{
				return _blackboard;
			}
			set
			{
				if (_blackboard != value)
				{
					_blackboard = value;
					UpdateAllNodeBBFields();
					SendTaskOwnerDefaults();
				}
			}
		}

		public bool isRunning { get; private set; }

		public bool isPaused { get; private set; }

		public Transform nodesRoot
		{
			get
			{
				if (_nodesRoot == null)
				{
					_nodesRoot = new GameObject("__ALLNODES__").transform;
					_nodesRoot.gameObject.AddComponent<NodesRootUtility>().parentGraph = this;
				}
				if (_nodesRoot.parent != base.transform)
				{
					_nodesRoot.parent = base.transform;
				}
				_nodesRoot.gameObject.hideFlags = (doHide ? HideFlags.HideInHierarchy : HideFlags.None);
				_nodesRoot.localPosition = Vector3.zero;
				return _nodesRoot;
			}
		}

		public static bool doHide
		{
			get
			{
				return true;
			}
		}

		protected void Awake()
		{
			MonoManager.Create();
			UpdateNodeIDsInGraph();
			ReorderNodeList();
		}

		protected void OnDestroy()
		{
			runningGraphs.Remove(this);
			MonoManager.current.RemoveMethod(OnGraphUpdate);
		}

		public void ReorderNodeList()
		{
			allNodes = allNodes.OrderBy((Node node) => node.ID).ToList();
		}

		public void SendTaskOwnerDefaults()
		{
			foreach (Task item in GetAllTasksOfType<Task>(true))
			{
				item.SetOwnerSystem(this);
			}
		}

		private void UpdateAllNodeBBFields()
		{
			foreach (Node allNode in allNodes)
			{
				allNode.UpdateNodeBBFields(blackboard);
			}
		}

		public void SendEvent(string eventName)
		{
			if (!string.IsNullOrEmpty(eventName) && isRunning && agent != null)
			{
				agent.gameObject.SendMessage("OnCustomEvent", eventName, SendMessageOptions.DontRequireReceiver);
			}
		}

		public static void SendGlobalEvent(string eventName)
		{
			foreach (Graph runningGraph in runningGraphs)
			{
				runningGraph.SendEvent(eventName);
			}
		}

		public new void SendMessage(string name)
		{
			SendMessage(name, null);
		}

		public new void SendMessage(string name, object argument)
		{
			if ((bool)agent)
			{
				agent.gameObject.SendMessage(name, argument, SendMessageOptions.DontRequireReceiver);
			}
			SendTaskMessage(name, argument);
		}

		public void SendTaskMessage(string name)
		{
			SendTaskMessage(name, null);
		}

		public void SendTaskMessage(string name, object argument)
		{
			foreach (Task item in GetAllTasksOfType<Task>(true))
			{
				MethodInfo methodInfo = item.GetType().NCGetMethod(name);
				if (methodInfo != null)
				{
					object[] parameters = ((methodInfo.GetParameters().Length == 0) ? null : new object[1] { argument });
					methodInfo.Invoke(item, parameters);
				}
			}
		}

		public void StartGraph()
		{
			StartGraph(agent, blackboard, null);
		}

		public void StartGraph(Action callback)
		{
			StartGraph(agent, blackboard, callback);
		}

		public void StartGraph(Component agent)
		{
			StartGraph(agent, blackboard, null);
		}

		public void StartGraph(Component agent, Action callback)
		{
			StartGraph(agent, blackboard, callback);
		}

		public void StartGraph(Component agent, Blackboard blackboard)
		{
			StartGraph(agent, blackboard, null);
		}

		public void StartGraph(Component agent, Blackboard blackboard, Action callback)
		{
			if (isRunning)
			{
				Debug.LogWarning("Graph allready Active");
				return;
			}
			if (primeNode == null)
			{
				Debug.LogWarning("You tried to Start a Graph that has no Start Node.", base.gameObject);
				return;
			}
			if (agent == null && !allowNullAgent)
			{
				Debug.LogWarning("You've tried to start a graph with null Agent.");
				return;
			}
			if (blackboard == null && agent != null)
			{
				Debug.Log(string.Concat("Graph started without blackboard. Looking for blackboard on agent '", agent.gameObject, "'..."), agent.gameObject);
				blackboard = agent.GetComponent<Blackboard>();
				if (blackboard != null)
				{
					Debug.Log("Blackboard found");
				}
			}
			UpdateNodeIDsInGraph();
			ReorderNodeList();
			this.blackboard = blackboard;
			this.agent = agent;
			if (callback != null)
			{
				FinishCallback = callback;
			}
			isRunning = true;
			runningGraphs.Add(this);
			if (!isPaused)
			{
				timeStarted = Time.time;
				foreach (Node allNode in allNodes)
				{
					allNode.OnGraphStarted();
				}
			}
			SendTaskMessage("OnGraphStarted");
			MonoManager.current.AddMethod(OnGraphUpdate);
			OnGraphStarted();
			isPaused = false;
		}

		protected virtual void OnGraphStarted()
		{
		}

		protected virtual void OnGraphUpdate()
		{
		}

		public void StopGraph()
		{
			if (!isRunning && !isPaused)
			{
				return;
			}
			MonoManager.current.RemoveMethod(OnGraphUpdate);
			isRunning = false;
			isPaused = false;
			runningGraphs.Remove(this);
			OnGraphStoped();
			foreach (Node allNode in allNodes)
			{
				allNode.ResetNode(false);
				allNode.OnGraphStoped();
			}
			SendTaskMessage("OnGraphStoped");
			if (FinishCallback != null)
			{
				FinishCallback();
			}
			FinishCallback = null;
		}

		protected virtual void OnGraphStoped()
		{
		}

		public void PauseGraph()
		{
			if (!isRunning)
			{
				return;
			}
			MonoManager.current.RemoveMethod(OnGraphUpdate);
			isRunning = false;
			isPaused = true;
			runningGraphs.Remove(this);
			OnGraphPaused();
			foreach (Node allNode in allNodes)
			{
				allNode.OnGraphPaused();
			}
			SendTaskMessage("OnGraphPaused");
		}

		protected virtual void OnGraphPaused()
		{
		}

		public Node GetNodeWithID(int searchID)
		{
			if (searchID <= allNodes.Count && searchID >= 0)
			{
				foreach (Node allNode in allNodes)
				{
					if (allNode.ID == searchID)
					{
						return allNode;
					}
				}
			}
			return null;
		}

		public List<T> GetAllNodesOfType<T>() where T : Node
		{
			return allNodes.OfType<T>().ToList();
		}

		public T GetNodeWithTag<T>(string name) where T : Node
		{
			foreach (T item in allNodes.OfType<T>())
			{
				T current = item;
				if (current.tagName == name)
				{
					return current;
				}
			}
			return (T)null;
		}

		public List<T> GetNodesWithTag<T>(string name) where T : Node
		{
			List<T> list = new List<T>();
			foreach (T item in allNodes.OfType<T>())
			{
				T current = item;
				if (current.tagName == name)
				{
					list.Add(current);
				}
			}
			return list;
		}

		public List<T> GetAllTagedNodes<T>() where T : Node
		{
			List<T> list = new List<T>();
			foreach (T item in allNodes.OfType<T>())
			{
				T current = item;
				if (!string.IsNullOrEmpty(current.tagName))
				{
					list.Add(current);
				}
			}
			return list;
		}

		public T GetNodeWithName<T>(string name) where T : Node
		{
			foreach (T item in allNodes.OfType<T>())
			{
				T current = item;
				if (StripNameColor(current.nodeName).ToLower() == name.ToLower())
				{
					return current;
				}
			}
			return (T)null;
		}

		private string StripNameColor(string name)
		{
			if (name.StartsWith("<") && name.EndsWith(">"))
			{
				name = name.Replace(name.Substring(0, name.IndexOf(">") + 1), string.Empty);
				name = name.Replace(name.Substring(name.IndexOf("<"), name.LastIndexOf(">") + 1 - name.IndexOf("<")), string.Empty);
			}
			return name;
		}

		public List<Node> GetRootNodes()
		{
			List<Node> list = new List<Node>();
			foreach (Node allNode in allNodes)
			{
				if (allNode.inConnections.Count == 0)
				{
					list.Add(allNode);
				}
			}
			return list;
		}

		public List<T> GetAllTasksOfType<T>(bool includeNested) where T : Task
		{
			return ((!includeNested) ? nodesRoot : base.transform).GetComponentsInChildren<T>(true).ToList();
		}

		public List<T> GetAllNestedGraphs<T>(bool recursive) where T : Graph
		{
			List<T> list = new List<T>();
			foreach (INestedNode item in allNodes.OfType<INestedNode>())
			{
				if (item.nestedGraph != null && !list.Contains((T)item.nestedGraph))
				{
					if (item.nestedGraph is T)
					{
						list.Add((T)item.nestedGraph);
					}
					if (recursive)
					{
						list.AddRange(item.nestedGraph.GetAllNestedGraphs<T>(recursive));
					}
				}
			}
			return list;
		}

		public Dictionary<BBVariable, object> GetDefinedBBVariables()
		{
			List<object> list = new List<object>();
			list.AddRange(GetAllTasksOfType<Task>(false).Cast<object>());
			list.AddRange(GetAllNodesOfType<Node>().Cast<object>());
			Dictionary<BBVariable, object> dictionary = new Dictionary<BBVariable, object>();
			foreach (object item in list)
			{
				FieldInfo[] array = item.GetType().NCGetFields();
				foreach (FieldInfo fieldInfo in array)
				{
					if (typeof(BBVariable).NCIsAssignableFrom(fieldInfo.FieldType))
					{
						BBVariable bBVariable = fieldInfo.GetValue(item) as BBVariable;
						if (bBVariable != null && bBVariable.useBlackboard && !bBVariable.isNone)
						{
							dictionary[bBVariable] = item;
						}
					}
					if (fieldInfo.FieldType != typeof(BBVariableSet))
					{
						continue;
					}
					BBVariableSet bBVariableSet = fieldInfo.GetValue(item) as BBVariableSet;
					if (bBVariableSet != null)
					{
						BBVariable selectedBBVariable = bBVariableSet.selectedBBVariable;
						if (selectedBBVariable != null && selectedBBVariable.useBlackboard && !selectedBBVariable.isNone)
						{
							dictionary[selectedBBVariable] = item;
						}
					}
				}
			}
			return dictionary;
		}

		public void UpdateNodeIDsInGraph()
		{
			int lastID = 0;
			if (primeNode != null)
			{
				lastID = primeNode.AssignIDToGraph(this, lastID);
			}
			for (int i = 0; i < allNodes.Count; i++)
			{
				if (allNodes[i].inConnections.Count == 0)
				{
					lastID = allNodes[i].AssignIDToGraph(this, lastID);
				}
			}
			for (int j = 0; j < allNodes.Count; j++)
			{
				allNodes[j].ResetRecursion();
			}
		}

		[Obsolete("Use 'AddNode' instead")]
		public Node AddNewNode(Type nodeType)
		{
			return AddNode(nodeType);
		}

		public T AddNode<T>() where T : Node
		{
			return AddNode(typeof(T)) as T;
		}

		public Node AddNode(Type nodeType)
		{
			if (!baseNodeType.NCIsAssignableFrom(nodeType))
			{
				Debug.Log(string.Concat(nodeType, " can't be added to ", GetType(), " graph"));
				return null;
			}
			Node node = Node.Create(this, nodeType);
			allNodes.Add(node);
			if (primeNode == null)
			{
				primeNode = node;
			}
			UpdateNodeIDsInGraph();
			return node;
		}

		public void RemoveNode(int id)
		{
			RemoveNode(GetNodeWithID(id));
		}

		public void RemoveNode(Node node)
		{
			if (!allNodes.Contains(node))
			{
				Debug.LogWarning("Node is not part of this graph", base.gameObject);
				return;
			}
			Connection[] array = node.outConnections.ToArray();
			foreach (Connection connection in array)
			{
				RemoveConnection(connection);
			}
			Connection[] array2 = node.inConnections.ToArray();
			foreach (Connection connection2 in array2)
			{
				RemoveConnection(connection2);
			}
			allNodes.Remove(node);
			UnityEngine.Object.DestroyImmediate(node.gameObject, true);
			UpdateNodeIDsInGraph();
			if (node == primeNode)
			{
				primeNode = GetNodeWithID(1);
			}
		}

		public Connection ConnectNode(Node sourceNode, Node targetNode)
		{
			return ConnectNode(sourceNode, targetNode, sourceNode.outConnections.Count);
		}

		public Connection ConnectNode(Node sourceNode, Node targetNode, int indexToInsert)
		{
			if (!targetNode.IsNewConnectionAllowed(sourceNode))
			{
				return null;
			}
			Connection result = Connection.Create(sourceNode, targetNode, indexToInsert);
			sourceNode.OnPortConnected(indexToInsert);
			UpdateNodeIDsInGraph();
			return result;
		}

		public void RemoveConnection(Connection connection)
		{
			connection.sourceNode.OnPortDisconnected(connection.sourceNode.outConnections.IndexOf(connection));
			if (Application.isPlaying)
			{
				connection.ResetConnection();
			}
			connection.sourceNode.outConnections.Remove(connection);
			connection.targetNode.inConnections.Remove(connection);
			UnityEngine.Object.DestroyImmediate(connection.gameObject, true);
			UpdateNodeIDsInGraph();
		}
	}
}
